@juangadm/pre-post 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -182
- package/dist/baseline.d.ts +122 -3
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +491 -46
- package/dist/baseline.js.map +1 -1
- package/dist/bin/cli.js +41 -17
- package/dist/bin/cli.js.map +1 -1
- package/dist/browser.d.ts +38 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +132 -11
- package/dist/browser.js.map +1 -1
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +5 -5
- package/dist/commands/compare.js.map +1 -1
- package/dist/commands/detect.d.ts +5 -0
- package/dist/commands/detect.d.ts.map +1 -1
- package/dist/commands/detect.js +3 -0
- package/dist/commands/detect.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +9 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/pr.d.ts +13 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +222 -107
- package/dist/commands/pr.js.map +1 -1
- package/dist/comparison.d.ts +14 -0
- package/dist/comparison.d.ts.map +1 -1
- package/dist/comparison.js +48 -25
- package/dist/comparison.js.map +1 -1
- package/dist/diff-worker.d.ts +1 -0
- package/dist/diff-worker.d.ts.map +1 -1
- package/dist/diff-worker.js +1 -0
- package/dist/diff-worker.js.map +1 -1
- package/dist/diff.d.ts +42 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +142 -24
- package/dist/diff.js.map +1 -1
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +5 -1
- package/dist/doctor.js.map +1 -1
- package/dist/drift.d.ts +29 -0
- package/dist/drift.d.ts.map +1 -0
- package/dist/drift.js +106 -0
- package/dist/drift.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -1
- package/dist/github.d.ts +34 -3
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +102 -11
- package/dist/github.js.map +1 -1
- package/dist/report.d.ts +27 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +144 -38
- package/dist/report.js.map +1 -1
- package/dist/routes/imports.d.ts +21 -1
- package/dist/routes/imports.d.ts.map +1 -1
- package/dist/routes/imports.js +32 -2
- package/dist/routes/imports.js.map +1 -1
- package/dist/routes.d.ts +18 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +110 -29
- package/dist/routes.js.map +1 -1
- package/dist/run.d.ts +46 -12
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +138 -17
- package/dist/run.js.map +1 -1
- package/dist/sheet.d.ts +24 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +94 -0
- package/dist/sheet.js.map +1 -0
- package/dist/timings.d.ts +46 -0
- package/dist/timings.d.ts.map +1 -0
- package/dist/timings.js +108 -0
- package/dist/timings.js.map +1 -0
- package/dist/types.d.ts +86 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/skill/SKILL.md +34 -10
package/README.md
CHANGED
|
@@ -1,214 +1,84 @@
|
|
|
1
1
|
# pre-post
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| | Pre (the baseline) | Post (this branch) |
|
|
10
|
-
|---|---|---|
|
|
11
|
-
| 1 | `--before` | `--after` |
|
|
12
|
-
| 2 | `before` in `.pre-post.json` | the preview deployment for this commit |
|
|
13
|
-
| 3 | the production deployment for the base commit | a local dev server |
|
|
14
|
-
| 4 | whatever is on production now | |
|
|
15
|
-
| 5 | the base commit, served locally | |
|
|
16
|
-
|
|
17
|
-
Rows 2 to 4 need no dev environment at all, which is the point: a preview deployment and a
|
|
18
|
-
production URL are enough for anyone who can open the PR. Deployments come from the GitHub
|
|
19
|
-
Deployments API, so Vercel, Cloudflare Pages, Netlify and Render all work with no extra
|
|
20
|
-
token and nothing to configure; a host that records only a commit status is read from the
|
|
21
|
-
deployment bot's own PR comment instead. Row 4 covers repositories that do not deploy every
|
|
22
|
-
push to their default branch, so nothing is deployed at the fork point — it prints which
|
|
23
|
-
commit Pre actually came from rather than implying the base.
|
|
24
|
-
|
|
25
|
-
pre-post never guesses a baseline. If no deployment can be found it says so and tells you
|
|
26
|
-
the one flag that fixes it, because a baseline that is quietly the wrong site reads as 100%
|
|
27
|
-
changed on every route and looks exactly like a real result.
|
|
28
|
-
|
|
29
|
-
The last baseline needs no network at all: it checks the base commit into a throwaway
|
|
30
|
-
worktree and boots its dev script. That keeps pre-post working inside a sandboxed agent
|
|
31
|
-
container, a CI job, or behind an egress allowlist — and it compares against exactly what
|
|
32
|
-
the branch forked from, rendered in the same browser as the Post side.
|
|
33
|
-
|
|
34
|
-
> Originally forked from [before-and-after](https://github.com/vercel-labs/before-and-after) by James Clements / Vercel Labs.
|
|
3
|
+
**See the change before you read the diff.**
|
|
4
|
+
|
|
5
|
+
pre-post puts before/after screenshots of every page your branch touched at the top of the
|
|
6
|
+
pull request. One command finds the changed routes, captures Pre and Post, highlights what
|
|
7
|
+
moved, and updates the PR description. No setup, no pasting images.
|
|
35
8
|
|
|
36
9
|
```
|
|
37
10
|
$ npx -y @juangadm/pre-post@latest pr
|
|
38
11
|
|
|
39
12
|
Routes (nextjs-app, 41ms): /, /pricing
|
|
40
|
-
|
|
41
|
-
/pricing medium src/app/pricing/page.tsx imports src/components/ui/button.tsx (2 hops)
|
|
42
|
-
Capturing 8 screenshots (2 route(s) × 2 viewport(s)) ...
|
|
43
|
-
changed /pricing @ mobile (1.42%, 1811ms)
|
|
13
|
+
Capturing 4 screenshots (2 route(s) × 1 viewport(s)) ...
|
|
44
14
|
changed /pricing @ desktop (0.64%, 2036ms)
|
|
45
|
-
same / @ mobile (0.00%, 1990ms)
|
|
46
15
|
same / @ desktop (0.00%, 2211ms)
|
|
47
|
-
Publishing 10 image(s) to acme/web@pre-post-assets ...
|
|
48
16
|
Updated PR description: https://github.com/acme/web/pull/42
|
|
49
|
-
pre-post · PR #42 · 2 route(s) · 2 viewport(s) · 6.8s
|
|
50
|
-
/ desktop no change
|
|
51
|
-
/ mobile no change
|
|
52
|
-
/pricing desktop 0.64% changed
|
|
53
|
-
/pricing mobile 1.4% changed
|
|
54
|
-
Comment: https://github.com/acme/web/pull/42
|
|
55
17
|
```
|
|
56
18
|
|
|
57
|
-
##
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
sentence rather than reporting an empty diff, and `--base <ref>` names the commit
|
|
63
|
-
directly. Then it follows the import
|
|
64
|
-
graph: a change to `components/ui/button.tsx` marks every page that imports it. Next.js
|
|
65
|
-
App Router and Pages Router, Vite apps (React Router, file-based `src/pages`), and a
|
|
66
|
-
generic fallback. Monorepos are handled by picking the app that owns the changed files.
|
|
67
|
-
2. **Capture.** Playwright + Chromium headless shell. The page's own clock is held
|
|
68
|
-
still while it loads and then run forward by a fixed budget, so a page that animates
|
|
69
|
-
on a timer is photographed at the same frame on both sides however fast each host
|
|
70
|
-
answered. Reduced motion, animations finished, caret hidden, fonts and images settled,
|
|
71
|
-
layout stable, lazy content primed. 2x device scale, full page (capped at 2400 CSS
|
|
72
|
-
px), desktop (add mobile with `-r`).
|
|
73
|
-
All routes and viewports run concurrently.
|
|
74
|
-
3. **Diff.** Pure-JS pixel comparison in worker threads. Reports the percentage changed,
|
|
75
|
-
the bounding box, and a tight crop of the changed region. A route counts as changed when
|
|
76
|
-
the painted difference covers at least `minChangedArea` CSS pixels² (default 100, roughly
|
|
77
|
-
a third of a 16px icon) or at least `threshold` of the canvas — the first rule is what
|
|
78
|
-
decides on a page, the second on a small image.
|
|
79
|
-
4. **Layout shift.** A padding change near the top of a page moves everything below it, and
|
|
80
|
-
pixel diffing counts every moved pixel as changed: a change a designer would call
|
|
81
|
-
"slightly roomier" reads as most of the page repainted, and the crop is suppressed just
|
|
82
|
-
when it would be most useful. So the two sides are checked for a single vertical offset
|
|
83
|
-
first — how far the content moved, and from which row. When one is found, Pre is re-spaced
|
|
84
|
-
into Post's layout and the two are compared there: the crop comes from that pair, and the
|
|
85
|
-
comment says `Content shifted down 48px` instead of quoting a percentage. Rows Post gained
|
|
86
|
-
are left as background rather than skipped, so content inserted above the shift — the
|
|
87
|
-
banner that caused it — still reads as new rather than as nothing. The offset is only
|
|
88
|
-
accepted when it accounts for most of the difference across the rows both sides share.
|
|
89
|
-
The raw numbers are left alone: a move is a visual change, and reporting less of one would
|
|
90
|
-
hide it. Reflow, where content moves both across and down, has no single offset to find,
|
|
91
|
-
so it is reported the way it always was.
|
|
92
|
-
5. **Publish.** Images go to a `pre-post-assets` branch in the same repository via the GitHub
|
|
93
|
-
API, as one commit per run. Nothing is committed to the PR branch, no CI is triggered,
|
|
94
|
-
and the blob URLs render on private repos — a screenshot is visible to exactly whoever
|
|
95
|
-
can see the repository. `pre-post prune` removes images for PRs closed more than 90 days
|
|
96
|
-
ago, but note that it commits a deletion rather than rewriting history: the older commits
|
|
97
|
-
still hold the blobs, so a link handed out earlier keeps working. Treat anything captured
|
|
98
|
-
as permanent, and think twice before pointing pre-post at a preview holding real data.
|
|
99
|
-
6. **Describe.** The images go in a delimited block at the top of the PR description,
|
|
100
|
-
replaced in place on every run and leaving your own text untouched. Changed routes show
|
|
101
|
-
a Pre/Post crop with the full page collapsed underneath; unchanged routes fold into a
|
|
102
|
-
single line. If the PR cannot be edited — a fork, a read-only token — it falls back to
|
|
103
|
-
one sticky comment.
|
|
104
|
-
|
|
105
|
-
## Install
|
|
106
|
-
|
|
107
|
-
Nothing to install. `npx -y @juangadm/pre-post@latest pr` downloads the CLI and, on first
|
|
108
|
-
use, the Chromium headless shell (~80 MB). You need Node 20+ and a GitHub token: either
|
|
109
|
-
`gh auth login` or `GH_TOKEN`.
|
|
110
|
-
|
|
111
|
-
As a Claude Code skill:
|
|
19
|
+
## Get started
|
|
20
|
+
|
|
21
|
+
Pick one. None of them installs anything permanently.
|
|
22
|
+
|
|
23
|
+
**In Claude Code**: install the skill once, then say `/pre-post` after a UI change.
|
|
112
24
|
|
|
113
25
|
```bash
|
|
114
26
|
npx skills add juangadm/pre-post -y
|
|
115
27
|
```
|
|
116
28
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
## Usage
|
|
29
|
+
**In a terminal**: run it on a branch with an open PR.
|
|
120
30
|
|
|
121
31
|
```bash
|
|
122
|
-
pre-post pr
|
|
123
|
-
pre-post pr --before https://acme.com # pin the baseline (saved to .pre-post.json)
|
|
124
|
-
pre-post pr --no-local-baseline # never build the base commit locally
|
|
125
|
-
pre-post pr --routes /pricing,/docs # explicit routes
|
|
126
|
-
pre-post pr --viewports desktop,1440x900 # custom viewports
|
|
127
|
-
pre-post pr --dry-run # capture + diff locally, post nothing
|
|
128
|
-
pre-post pr --json # machine-readable output
|
|
129
|
-
|
|
130
|
-
pre-post https://acme.com http://localhost:3000 --routes /pricing # ad-hoc comparison
|
|
131
|
-
pre-post before.png after.png # diff two images
|
|
132
|
-
pre-post detect # which routes does this branch touch?
|
|
133
|
-
pre-post login https://staging.acme.com # sign in once; the session is reused
|
|
134
|
-
pre-post prune --days 90 # clean up the assets branch
|
|
135
|
-
pre-post doctor # browser, token, dev server, config
|
|
32
|
+
npx -y @juangadm/pre-post@latest pr
|
|
136
33
|
```
|
|
137
34
|
|
|
138
|
-
|
|
139
|
-
|
|
35
|
+
**On every PR, automatically**: add the [GitHub Action](https://github.com/juangadm/pre-post/blob/main/docs/github-action.md). No tokens or
|
|
36
|
+
secrets needed.
|
|
140
37
|
|
|
141
|
-
|
|
38
|
+
### You'll need
|
|
142
39
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
| 1 | the run failed — every capture errored, or an unexpected error; for `doctor`, a required check failed |
|
|
147
|
-
| 2 | the arguments could not be parsed |
|
|
148
|
-
| 3 | something needs a human; the message says what |
|
|
149
|
-
|
|
150
|
-
`doctor` marks a check **FAIL** only when `pre-post pr` has no way to proceed without it —
|
|
151
|
-
the browser, a GitHub token, and being inside a git repository. Everything else prints as
|
|
152
|
-
**note**: no dev server running, or no `--before` saved, narrows *which* strategy a run
|
|
153
|
-
picks rather than stopping it, so those never change the exit code.
|
|
154
|
-
|
|
155
|
-
Results go into a delimited block at the top of the PR description, which re-runs replace in
|
|
156
|
-
place, leaving your own text untouched. If the PR cannot be edited — a fork, a read-only
|
|
157
|
-
token — it falls back to a single sticky comment.
|
|
158
|
-
|
|
159
|
-
## Configuration
|
|
160
|
-
|
|
161
|
-
Optional `.pre-post.json` in the repo root:
|
|
162
|
-
|
|
163
|
-
```json
|
|
164
|
-
{
|
|
165
|
-
"before": "https://acme.com",
|
|
166
|
-
"routes": ["/"],
|
|
167
|
-
"samples": { "/blog/[slug]": "/blog/hello-world" },
|
|
168
|
-
"viewports": ["desktop"],
|
|
169
|
-
"fullPage": true,
|
|
170
|
-
"maxHeight": 2400,
|
|
171
|
-
"scale": 2,
|
|
172
|
-
"threshold": 0.001,
|
|
173
|
-
"minChangedArea": 100,
|
|
174
|
-
"maxRoutes": 6,
|
|
175
|
-
"ignore": ["apps/docs"],
|
|
176
|
-
"headers": {},
|
|
177
|
-
"assetsBranch": "pre-post-assets"
|
|
178
|
-
}
|
|
179
|
-
```
|
|
40
|
+
- **Node 20+**
|
|
41
|
+
- **A GitHub token** with write access: `gh auth login`, or set `GH_TOKEN`
|
|
42
|
+
- **An open PR**: without one, pre-post prints the markdown for you to paste
|
|
180
43
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
| Variable | Purpose |
|
|
184
|
-
|---|---|
|
|
185
|
-
| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub token (default: `gh auth token`) |
|
|
186
|
-
| `VERCEL_AUTOMATION_BYPASS_SECRET` | Bypass Vercel Deployment Protection on preview and production URLs |
|
|
187
|
-
| `PRE_POST_CONCURRENCY` | Parallel pages (default 6) |
|
|
188
|
-
| `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` | Use a specific Chromium binary |
|
|
189
|
-
| `GH_REPO` | `owner/repo` when the remote URL cannot be parsed |
|
|
44
|
+
The first run downloads a small browser (~80 MB). Stuck? Run
|
|
45
|
+
`npx -y @juangadm/pre-post@latest doctor`.
|
|
190
46
|
|
|
191
|
-
##
|
|
47
|
+
## Everyday commands
|
|
192
48
|
|
|
193
|
-
```
|
|
194
|
-
|
|
49
|
+
```bash
|
|
50
|
+
pre-post pr # screenshot this branch's PR
|
|
51
|
+
pre-post pr --routes /pricing,/docs # only these pages
|
|
52
|
+
pre-post pr -r # desktop and mobile
|
|
53
|
+
pre-post pr --before https://acme.com # set the "before" site (remembered)
|
|
54
|
+
pre-post pr --dry-run # try it without posting
|
|
55
|
+
pre-post login https://staging.acme.com # sign in once for protected sites
|
|
56
|
+
pre-post doctor # check your setup
|
|
195
57
|
```
|
|
196
58
|
|
|
197
|
-
|
|
59
|
+
If pre-post needs something from you, it stops with one sentence saying exactly what.
|
|
198
60
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
61
|
+
## How it decides what "before" is
|
|
62
|
+
|
|
63
|
+
Post (your branch) is the PR's preview deployment, or a local dev server. Pre is the
|
|
64
|
+
production site your branch forked from. When nothing is deployed, pre-post builds the base
|
|
65
|
+
commit itself, so it works in CI and in sandboxes too. It never guesses: if it can't find a
|
|
66
|
+
trustworthy baseline, it tells you which flag to pass.
|
|
205
67
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
68
|
+
Screenshots are stored on a `pre-post-assets` branch in your own repo, so only people who can
|
|
69
|
+
see the repo can see them. Nothing is committed to your PR branch.
|
|
70
|
+
|
|
71
|
+
## Learn more
|
|
72
|
+
|
|
73
|
+
| | |
|
|
74
|
+
|---|---|
|
|
75
|
+
| [How it works](https://github.com/juangadm/pre-post/blob/main/docs/how-it-works.md) | How Pre and Post are chosen, capture, diffing, layout shifts |
|
|
76
|
+
| [GitHub Action](https://github.com/juangadm/pre-post/blob/main/docs/github-action.md) | Run on every PR with no per-person setup |
|
|
77
|
+
| [Reference](https://github.com/juangadm/pre-post/blob/main/docs/reference.md) | All commands, exit codes, `.pre-post.json`, environment variables |
|
|
78
|
+
| [Screenshot storage](https://github.com/juangadm/pre-post/blob/main/docs/storage.md) | How much space it uses and how to prune it |
|
|
79
|
+
| [Contributing](https://github.com/juangadm/pre-post/blob/main/CONTRIBUTING.md) | Build and test locally |
|
|
211
80
|
|
|
212
|
-
##
|
|
81
|
+
## Credits
|
|
213
82
|
|
|
214
|
-
|
|
83
|
+
Forked from [before-and-after](https://github.com/vercel-labs/before-and-after) by James
|
|
84
|
+
Clements / Vercel Labs. MIT licensed.
|
package/dist/baseline.d.ts
CHANGED
|
@@ -11,12 +11,24 @@
|
|
|
11
11
|
* The cost is time: a checkout, an install and a dev-server boot. So callers
|
|
12
12
|
* reach for it after a reachable deployment, not before one.
|
|
13
13
|
*/
|
|
14
|
+
import { ChildProcess } from 'child_process';
|
|
14
15
|
import { NeedsHumanError } from './errors.js';
|
|
16
|
+
import { Stopwatch } from './timings.js';
|
|
15
17
|
export interface LocalBaseline {
|
|
16
18
|
url: string;
|
|
17
19
|
/** Stop the dev server and delete the worktree. Safe to call twice. */
|
|
18
20
|
stop: () => Promise<void>;
|
|
21
|
+
/** Everything the dev server printed, so a failure can be explained with its own words. */
|
|
22
|
+
logFile?: string;
|
|
19
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Where a dev server's output goes: a file per server, never the terminal.
|
|
26
|
+
*
|
|
27
|
+
* It used to go nowhere (`stdio: 'ignore'`), so when a server answered 500
|
|
28
|
+
* there was no way to say why. A file rather than a pipe: the server is
|
|
29
|
+
* detached and must not stall when this process stops reading.
|
|
30
|
+
*/
|
|
31
|
+
export declare function serverLogFile(side: 'pre' | 'post', port: number): string;
|
|
20
32
|
interface PackageManager {
|
|
21
33
|
bin: string;
|
|
22
34
|
install: string[];
|
|
@@ -105,6 +117,27 @@ export declare function servableDir(treeRoot: string, appPrefix?: string): {
|
|
|
105
117
|
* are exactly the ones that hold secrets.
|
|
106
118
|
*/
|
|
107
119
|
export declare function copyEnvFiles(from: string, to: string, appPrefix?: string): string[];
|
|
120
|
+
/** Does this variable name the app's own origin? */
|
|
121
|
+
export declare function isOriginKey(key: string): boolean;
|
|
122
|
+
/** One env file's text with every origin variable pointed at `origin`. */
|
|
123
|
+
export declare function rewriteEnvOrigins(text: string, origin: string): {
|
|
124
|
+
text: string;
|
|
125
|
+
keys: string[];
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Point the worktree's copied env files at the baseline's real origin.
|
|
129
|
+
*
|
|
130
|
+
* The copies name the port the developer's own dev server uses, and the
|
|
131
|
+
* baseline is on an ephemeral one. An app whose auth library is told it lives
|
|
132
|
+
* at :3000 while it is answering on :51234 rejects its own callbacks: the page
|
|
133
|
+
* renders, the session never resolves, and every capture is a loading
|
|
134
|
+
* skeleton. That reads as success — a screenshot came back — which is the
|
|
135
|
+
* failure mode this whole file exists to avoid.
|
|
136
|
+
*
|
|
137
|
+
* Same constraint as the copy: only ever writes inside `dir`, and returns
|
|
138
|
+
* names, never values.
|
|
139
|
+
*/
|
|
140
|
+
export declare function pointEnvFilesAt(dir: string, origin: string, appPrefix?: string): string[];
|
|
108
141
|
/** An OS-assigned free port. */
|
|
109
142
|
export declare function freePort(): Promise<number>;
|
|
110
143
|
/**
|
|
@@ -127,7 +160,7 @@ export interface InstallResult {
|
|
|
127
160
|
/** In order. Length 2 when a first failure earned a retry. */
|
|
128
161
|
attempts: InstallAttempt[];
|
|
129
162
|
}
|
|
130
|
-
export type InstallRunner = (bin: string, argv: string[], cwd: string, timeoutMs: number) => InstallAttempt
|
|
163
|
+
export type InstallRunner = (bin: string, argv: string[], cwd: string, timeoutMs: number, signal?: AbortSignal) => Promise<InstallAttempt>;
|
|
131
164
|
/**
|
|
132
165
|
* Does this look like npm refusing to resolve a peer range?
|
|
133
166
|
*
|
|
@@ -146,11 +179,11 @@ export declare function isPeerConflict(output: string): boolean;
|
|
|
146
179
|
* the comparison this tool exists to make is between two renders of the same
|
|
147
180
|
* app, not between two dependency trees.
|
|
148
181
|
*/
|
|
149
|
-
export declare function installDeps(pm: PackageManager, cwd: string, timeoutMs: number, run?: InstallRunner): InstallResult
|
|
182
|
+
export declare function installDeps(pm: PackageManager, cwd: string, timeoutMs: number, run?: InstallRunner, signal?: AbortSignal): Promise<InstallResult>;
|
|
150
183
|
/**
|
|
151
184
|
* The install failed and nothing else can rescue this run.
|
|
152
185
|
*
|
|
153
|
-
* Its own error rather than a quiet null, for the reason docs/portability.md
|
|
186
|
+
* Its own error rather than a quiet null, for the reason docs/internal/portability.md
|
|
154
187
|
* §1 gives about base resolution: a run that compared nothing must not exit
|
|
155
188
|
* clean. A null here fell through to "no baseline", and on a repository with a
|
|
156
189
|
* configured production URL it fell through to comparing against that instead
|
|
@@ -175,17 +208,103 @@ export declare class BaselineInstallError extends NeedsHumanError {
|
|
|
175
208
|
readonly result: InstallResult;
|
|
176
209
|
constructor(result: InstallResult, where: string, ranIn: string);
|
|
177
210
|
}
|
|
211
|
+
/** A command to run between the install and the dev server. */
|
|
212
|
+
export interface SetupStep {
|
|
213
|
+
/** The command as a reader would type it, for the log. */
|
|
214
|
+
label: string;
|
|
215
|
+
bin: string;
|
|
216
|
+
argv: string[];
|
|
217
|
+
cwd: string;
|
|
218
|
+
/** Run through a shell, for a command a human wrote as one string. */
|
|
219
|
+
shell?: boolean;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Build the workspace packages the app imports, before the dev server starts.
|
|
223
|
+
*
|
|
224
|
+
* A monorepo app depends on sibling packages that ship compiled output, and a
|
|
225
|
+
* fresh install does not produce it — `turbo dev` normally would, but the dev
|
|
226
|
+
* script this file runs is the app's own. So the app boots against a package
|
|
227
|
+
* with no `dist`, and the baseline is an error page or a shell.
|
|
228
|
+
*
|
|
229
|
+
* `^...` is turbo's "dependencies of, excluding itself": the siblings get
|
|
230
|
+
* built, the app is left to the dev server.
|
|
231
|
+
*/
|
|
232
|
+
export declare function turboDependencyBuild(treeRoot: string, appDir: string): SetupStep | null;
|
|
233
|
+
/**
|
|
234
|
+
* What to run between install and dev: `baselineSetup` if the repository
|
|
235
|
+
* configured one, else turbo's dependency build when this is a turborepo.
|
|
236
|
+
*
|
|
237
|
+
* The configured command wins outright. It is the general answer — one line in
|
|
238
|
+
* `.pre-post.json` for a repository whose baseline needs a codegen step, a
|
|
239
|
+
* prisma generate, a build this tool has never heard of — and a repository
|
|
240
|
+
* that names one has already told us the guess is not wanted.
|
|
241
|
+
*/
|
|
242
|
+
export declare function setupStep(treeRoot: string, appDir: string, configured?: string): SetupStep | null;
|
|
243
|
+
/**
|
|
244
|
+
* The configured setup command failed, and nothing else can rescue this run.
|
|
245
|
+
*
|
|
246
|
+
* One sentence, and the output goes to the log above it, for the reason
|
|
247
|
+
* `installFailureHint` gives. The directory named is the caller's own, never
|
|
248
|
+
* the worktree: cleanup has deleted the worktree by the time anyone reads it.
|
|
249
|
+
*/
|
|
250
|
+
export declare function setupFailureHint(label: string, ranIn: string): string;
|
|
251
|
+
export declare class BaselineSetupError extends NeedsHumanError {
|
|
252
|
+
constructor(label: string, ranIn: string);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Can the base commit use the dependencies this checkout already installed?
|
|
256
|
+
*
|
|
257
|
+
* Only when no install input differs between the base and the working tree,
|
|
258
|
+
* uncommitted edits included, and a lockfile is tracked (without one there is
|
|
259
|
+
* nothing to prove the trees match). Returns the package directories whose
|
|
260
|
+
* node_modules to bring across, or null to install as usual. Once the diff is
|
|
261
|
+
* clean, the index lists the same package.json files the base has, so it
|
|
262
|
+
* answers that without walking the base commit's whole tree.
|
|
263
|
+
*/
|
|
264
|
+
export declare function reusableInstall(repoRoot: string, sha: string): Promise<string[] | null>;
|
|
265
|
+
/**
|
|
266
|
+
* Bring this checkout's installed dependencies into the base worktree.
|
|
267
|
+
* True when every one was copied; on any failure the partial copies are
|
|
268
|
+
* removed so the normal install starts from a clean tree.
|
|
269
|
+
*/
|
|
270
|
+
export declare function reuseInstall(repoRoot: string, worktree: string, dirs: string[]): Promise<boolean>;
|
|
271
|
+
/** How long a dev server gets to exit on SIGTERM before it is killed. */
|
|
272
|
+
export declare const SERVER_STOP_TIMEOUT_MS = 5000;
|
|
273
|
+
/**
|
|
274
|
+
* SIGTERM the server's process group, wait for it to exit, then make sure.
|
|
275
|
+
*
|
|
276
|
+
* The wait is on the child itself — the package manager, which exits once the
|
|
277
|
+
* server under it has — because Node reaps it, so its exit is a fact. Asking
|
|
278
|
+
* the group with signal 0 was not: in a container whose PID 1 does not reap,
|
|
279
|
+
* dead members linger as zombies that still answer, and every stop waited out
|
|
280
|
+
* the full timeout. Once the child is gone, or `timeoutMs` has passed, the
|
|
281
|
+
* whole group is SIGKILLed, so nothing it started is left writing into a
|
|
282
|
+
* worktree about to be removed.
|
|
283
|
+
*/
|
|
284
|
+
export declare function stopGroup(child: ChildProcess, timeoutMs?: number): Promise<void>;
|
|
178
285
|
export interface BaselineOptions {
|
|
179
286
|
repoRoot: string;
|
|
180
287
|
/** Commit to serve. Omit to serve the working tree as it stands. */
|
|
181
288
|
sha?: string;
|
|
182
289
|
/** Directory holding the app's package.json, relative to the repo root. */
|
|
183
290
|
appPrefix?: string;
|
|
291
|
+
/**
|
|
292
|
+
* Command to run between the install and the dev server, from
|
|
293
|
+
* `.pre-post.json`. Omit to let a turborepo's dependency build be inferred.
|
|
294
|
+
*/
|
|
295
|
+
setup?: string;
|
|
184
296
|
/** Budget for install + boot. */
|
|
185
297
|
timeoutMs?: number;
|
|
186
298
|
/** Injectable for tests; defaults to a real PATH scan. */
|
|
187
299
|
pathHas?: (bin: string) => boolean;
|
|
188
300
|
log?: (msg: string) => void;
|
|
301
|
+
/** Records how long each step took, under `pre.*` or `post.*`. */
|
|
302
|
+
timings?: Stopwatch;
|
|
303
|
+
/**
|
|
304
|
+
* Abort the boot: the caller no longer needs this side. Whatever it started
|
|
305
|
+
* is stopped and cleaned up, and it resolves null.
|
|
306
|
+
*/
|
|
307
|
+
signal?: AbortSignal;
|
|
189
308
|
}
|
|
190
309
|
/**
|
|
191
310
|
* Check out `sha` into a throwaway worktree, install, and start its dev server.
|
package/dist/baseline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
|